package org.az.netty_study.p13_自定义协议;

import cn.hutool.json.JSONUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * 自定义编解码类
 * 在自定义类与字节数组之间转换
 * ByteToMessageCodec本质是一个ChannelDuplexHandler，也就是入站和出站handler
 */
@Slf4j
public class CustomizeCodec extends ByteToMessageCodec<CustomizeMessage> {
    /**
     * 协议应该有如下几个部分
     *  - 魔数，用来在第一时间判定是否是无效数据包
     *  - 版本号，可以支持协议的升级
     *  - 序列化算法，消息正文到底采用哪种序列化反序列化方式，可以由此扩展，例如：json、protobuf、hessian、jdk
     *  - 指令类型，是登录、注册、单聊、群聊... 跟业务相关
     *  - 请求序号，为了双工通信，提供异步能力
     *  - 正文长度
     *  - 消息正文
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, CustomizeMessage msg, ByteBuf out) throws Exception {
        //写入魔数，8字节
        out.writeBytes("yangtong".getBytes());
        //写入版本号，1字节
        out.writeByte(1);
        //写入序列化算法，1个字节(这里规定0-jdk序列化,1-json序列化,2-xml序列化)
        out.writeByte(1);
        //写入指令类型，1个字节(这里规定0-登录，1-注销，2-聊天)
        out.writeByte(msg.getType());
        //写入请求序号，4字节
        out.writeInt(0);
        //对应填充，使前面所有部分占16个字节
        out.writeByte(0xFF);

        //序列化CustomizeMessage，这里使用json方式
        byte[] content = JSONUtil.toJsonStr(msg).getBytes();
        //写入正文长度，从下标16开始就是正文长度
        out.writeInt(content.length);
        //写入消息正文
        out.writeBytes(content);
    }

    /**
     * 编码时怎么编的，这就怎么解码
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
        //获取8个字节的魔数，存放到magicNum里
        byte[] magicNum = new byte[8];
        in.readBytes(magicNum, 0, 8);
        //获取1个字节的版本号
        byte version = in.readByte();
        //获取1个字节的序列化算法
        byte serializer = in.readByte();
        //获取指令类型，1个字节
        byte type = in.readByte();
        //读取序列号
        int rankNum = in.readInt();
        //对齐填充
        int fillByte = in.readByte();

        //获取正文长度
        int length = in.readInt();
        //再读取length字节的消息正文
        byte[] content = new byte[length];
        in.readBytes(content, 0, length);
        //根据序列化算法，反序列化消息正文字节数组
        if (serializer == 1) {
            String msg = new String(content);
            CustomizeMessage message = JSONUtil.toBean(msg, CustomizeMessage.class);
            log.info("{}-{}-{}-{}-{}-{}-{}-{}", new String(magicNum), version, serializer, type, rankNum, fillByte, length, message);
            out.add(message);
        }
    }
}
